package eu.performance.buffer.swap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.RandomAccessFile;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;

import eu.performance.buffer.RuntimeConfigure;

/**
 * 构建在本地文件上的交换文件类
 * 
 * @author WZZ
 * 
 * @param <T>
 */
public class SwapFileOverBasicIO<T> implements SwapFile<T> {

	/**
	 * 交换文件中每个交换页的大小（大于9），当前为256，单位Byte
	 */
	final static int PageSize = 256;

	File indexFile = null;
	File swapFile = null;

	int count=1;
	/**
	 * 索引文件随机读写对象
	 */
	RandomAccessFile indexOperator = null;

	/**
	 * 交换文件随机读写对象
	 */
	RandomAccessFile swapOperator = null;

	/**
	 * 空索引队列
	 */
	List<Integer> containerList = new LinkedList<Integer>();

	/**
	 * 空页队列
	 */
	List<Integer> pageList = new LinkedList<Integer>();

	/**
	 * 索引文件指针
	 */
	private int iP = 0;

	/**
	 * 交换页面指针
	 */
	private int sP = 0;

	SwapFileOverBasicIO() {
		String id = null;
		// 同步锁
		synchronized (SwapFileOverBasicIO.class) {
			// 随机文件名
			while (true) {
				id = String.valueOf(Math.random()).replace("0.", "");
				indexFile = new File(RuntimeConfigure.getBufferDirectory()+id + ".index");
				if (!indexFile.exists())
					break;
			}
			try {
				// 创建索引文件
				indexFile.createNewFile();
				indexFile.deleteOnExit();
				//System.out.println(indexFile.getAbsolutePath());
				indexOperator = new RandomAccessFile(indexFile, "rw");
				// 创建交换文件
				swapFile = new File(RuntimeConfigure.getBufferDirectory()+id + ".swap");
				swapFile.createNewFile();
				swapFile.deleteOnExit();
				swapOperator = new RandomAccessFile(swapFile, "rw");
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public int swapIn(Serializable object) throws IOException {
		// Step 1:找到一个容器
		if (!containerList.isEmpty()) {
			iP = containerList.remove(0);
		} else {
			long length = indexOperator.length() + 4;
			indexOperator.setLength(length);
			iP = (int) (length / 4 - 1);
		}
		indexOperator.seek(iP * 4);
		// Step 2:写入
		indexOperator.writeInt(this.writeToSwapFile(object));
		if(count%30==0){
			count=1;
		}
		return iP;
	}

	@Override
	public int swapIn(T object) throws NotSwapableException, IOException {
		if (!(object instanceof Serializable))
			throw new NotSwapableException();
		return swapIn((Serializable) object);
	}

	@Override
	public T swapOut(int index) throws IOException {
		//System.out.println("swap out index="+index);
		// 检查是否存在
		long pos = index * 4;
		if (pos >= indexOperator.length())
			return null;
		indexOperator.seek(pos);
		sP = indexOperator.readInt();
		//System.out.println("swap out pointer="+sP);
		if (sP < 0)
			return null;
		//清除index,将index加入到空索引队列中.
		indexOperator.seek(pos);
		indexOperator.writeInt(-1);
		containerList.add(index);
		try {
			return generateObject(read());
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			return null;
		}
	}

	@Override
	public boolean contains(int index) throws IOException {
		long pos = index * 4;
		if (pos >= indexOperator.length())
			return false;
		indexOperator.seek(pos);
		int flag = indexOperator.readInt();
		if (flag < 0)
			return false;
		return true;
	}

	private int writeToSwapFile(Serializable object) throws IOException {
		// step 1:找到一个起始页
		int startPage = 0;
		if (!pageList.isEmpty()) {
			sP = pageList.remove(0);
		} else {
			long length = swapOperator.length() + PageSize;
			swapOperator.setLength(length);
			sP = (int) (length / PageSize - 1);
		}
		startPage = sP;
		// step 2:获取对象序列化之后的字节数组
		byte[] bytes = getObjectBytes(object);
		// step 3:写入字节
		int tL = bytes.length;
		int wP = 0;
		int wL = 0;
		if (wP + PageSize - 9 > tL)
			wL = tL - wP;
		else
			wL = PageSize - 9;
		swapOperator.seek((long) sP * (long) PageSize);
		swapOperator.write(1);
		swapOperator.writeInt(tL);
		swapOperator.write(bytes, wP, wL);
		//System.out.println("write length:"+wL);
		wP += PageSize - 9;
		while (wP < tL - 1) {
			if (wP + PageSize - 5 > tL)
				wL = tL - wP;
			else
				wL = PageSize - 5;
			if (!pageList.isEmpty()) {
				sP = pageList.remove(0);
			} else {
				long length = swapOperator.length() + PageSize;
				swapOperator.setLength(length);
				sP = (int) (length / PageSize - 1);
			}
			swapOperator.writeInt(sP);
			//System.out.println("next page "+sP);
			swapOperator.seek((long) sP * (long) PageSize);
			swapOperator.write(1);
			swapOperator.write(bytes, wP, wL);
			//System.out.println("write length:"+wL);
			wP += PageSize - 5;
		}
		return startPage;
	}

	private byte[] read() throws IOException {
		swapOperator.seek(sP * PageSize);
		byte flag = swapOperator.readByte();
		if (flag < 0)
			return null;
		int length = swapOperator.readInt();// 数据总长
		//System.out.println("length="+length);
		int pos = 5;
		byte[] bytes = new byte[length];
		while (true) {
			length -= readPage(length, pos, bytes);
			if (length <= 0)
				break;
			pos = 1;
		}
		return bytes;
	}

	private int readPage(int leftLength, int startPos, byte[] bytes)
			throws IOException {
		swapOperator.seek(sP * PageSize + startPos);
		int len=PageSize - startPos - 4;
		if(len>leftLength)
			len=leftLength;
		len=swapOperator.read(bytes, bytes.length-leftLength, len);
		//System.out.println("read out "+len);
		int oP=sP;
		sP = swapOperator.readInt();
		cleanPage(oP);
		pageList.add(oP);
		return len;
	}

	private void cleanPage(int pointer) throws IOException{
//		byte[] bytes=new byte[PageSize];
//		for(int i=0;i<bytes.length;i++)
//			bytes[i]=-1;
//		swapOperator.seek(pointer * PageSize );
//		swapOperator.write(bytes);
	}
	
	private byte[] getObjectBytes(Serializable object) throws IOException {
		byte[] bytes = null;
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		ObjectOutputStream os = new ObjectOutputStream(bs);
		os.writeObject(object);
		bytes = bs.toByteArray();
		os.close();
		bs.close();
		return bytes;
	}

	@SuppressWarnings("unchecked")
	private T generateObject(byte[] bytes) throws IOException,
			ClassNotFoundException {
//		int i=0;
//		for(byte b:bytes){
//			i++;
//			System.out.print(Integer.toHexString(b).toUpperCase()+" ");
//			if(i%16==0)
//				//System.out.println();
//		}
		ObjectInputStream in = null;
		ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
		in = new ObjectInputStream(bin);
		T o = (T) in.readObject();
		in.close();
		return o;
	}

	@Override
	public void close() {
		try {
			indexOperator.close();
			swapOperator.close();
			indexFile.delete();
			swapFile.delete();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
